package com.duan.laugh.auth.config;

import com.duan.laugh.auth.service.impl.UserDetailsServiceImpl;
import com.duan.laugh.common.core.constants.CoreConstants;
import com.duan.laugh.common.security.constants.SecurityConstants;
import com.duan.laugh.common.security.exception.LaughOAuth2WebResponseExceptionTranslator;
import com.duan.laugh.common.security.util.LaughUser;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.data.redis.connection.RedisConnectionFactory;
import org.springframework.security.authentication.AuthenticationManager;
import org.springframework.security.crypto.password.PasswordEncoder;
import org.springframework.security.oauth2.common.DefaultOAuth2AccessToken;
import org.springframework.security.oauth2.config.annotation.configurers.ClientDetailsServiceConfigurer;
import org.springframework.security.oauth2.config.annotation.web.configuration.AuthorizationServerConfigurerAdapter;
import org.springframework.security.oauth2.config.annotation.web.configuration.EnableAuthorizationServer;
import org.springframework.security.oauth2.config.annotation.web.configurers.AuthorizationServerEndpointsConfigurer;
import org.springframework.security.oauth2.config.annotation.web.configurers.AuthorizationServerSecurityConfigurer;
import org.springframework.security.oauth2.provider.ClientDetailsService;
import org.springframework.security.oauth2.provider.client.JdbcClientDetailsService;
import org.springframework.security.oauth2.provider.token.TokenEnhancer;
import org.springframework.security.oauth2.provider.token.store.redis.RedisTokenStore;

import javax.annotation.Resource;
import javax.sql.DataSource;
import java.util.HashMap;
import java.util.Map;

/**
 * 授权服务配置
 * /oauth/authorize：验证接口， AuthorizationEndpoint
 * /oauth/token：获取token
 * /oauth/confirm_access：用户授权
 * /oauth/error：认证失败
 * /oauth/check_token：资源服务器用来校验token
 * /oauth/token_key：jwt模式下获取公钥；位于：TokenKeyEndpoint ，通过 JwtAccessTokenConverter 访问key
 *
 * @author duanjw
 */
@Configuration
@EnableAuthorizationServer
@Slf4j
public class AuthorizationServer extends AuthorizationServerConfigurerAdapter {

    /**
     * 在spring5之后，必须配置加密算法
     *
     * @return
     */
    @Autowired
    public PasswordEncoder passwordEncoder;
    @Autowired
    private AuthenticationManager authenticationManager;
    @Autowired
    private RedisConnectionFactory redisConnectionFactory;
    @Resource
    private DataSource dataSource;
    @Resource
    private UserDetailsServiceImpl userDetailsService;
    @Autowired
    private LaughOAuth2WebResponseExceptionTranslator laughOAuth2WebResponseExceptionTranslator;

    /**
     * client客户端的信息。包括权限范围、授权方式、客户端权限等配置
     * 授权方式有4种:implicit, client_redentials, password , authorization_code, 其中密码授权方式必须结合 AuthenticationManager 进行配置。
     * 必须至少配置一个客户端。
     *
     * @param clients
     * @throws Exception
     */
    @Override
    public void configure(ClientDetailsServiceConfigurer clients) throws Exception {
        // 客户端信息从数据库读取
        clients.withClientDetails(clientDetails());
    }

    /**
     * 配置AuthorizationServer 端点的非安全属性，也就是 token 存储方式、token 配置、用户授权模式等。
     * 默认不需做任何配置，除非使用 密码授权方式, 这时候必须配置 AuthenticationManager。
     *
     * @param endpoints
     */
    @Override
    public void configure(AuthorizationServerEndpointsConfigurer endpoints) {
        endpoints
                // 自定义token生成方式
                .tokenStore(redisTokenStore())
                .tokenEnhancer(tokenEnhancer())
                .authenticationManager(authenticationManager)
                // 刷新token后，原先的refresh_token不失效
                .reuseRefreshTokens(false).userDetailsService(userDetailsService);
        // 处理 ExceptionTranslationFilter 抛出的异常
        endpoints.exceptionTranslator(laughOAuth2WebResponseExceptionTranslator);
    }


    @Override
    public void configure(AuthorizationServerSecurityConfigurer oauthServer) {
        oauthServer
                .allowFormAuthenticationForClients()
                .checkTokenAccess("permitAll()");
    }

    /**
     * token增强，添加额外信息
     *
     * @return
     */
    @Bean
    public TokenEnhancer tokenEnhancer() {
        return (accessToken, authentication) -> {

            // 添加额外信息的map
            final Map<String, Object> additionMessage = new HashMap<>(3);
            final Object principal = authentication.getUserAuthentication().getPrincipal();
            // 获取当前登录的用户
            LaughUser user = (LaughUser) authentication.getUserAuthentication().getPrincipal();

            // 如果用户不为空 则把id放入jwt token中
            if (user != null) {
                additionMessage.put(SecurityConstants.DETAILS_USER_ID, user.getId());
                additionMessage.put(CoreConstants.DETAILS_TENANT_ID, user.getTenantId());
                additionMessage.put(CoreConstants.DETAILS_CREATE_TENANT_ID, user.getTenantId());
            }
            ((DefaultOAuth2AccessToken) accessToken).setAdditionalInformation(additionMessage);
            return accessToken;
        };
    }

    /**
     * 客户端从数据库读取
     *
     * @return
     */
    @Bean
    public ClientDetailsService clientDetails() {
        return new JdbcClientDetailsService(dataSource);
    }

    /**
     * 配置token存储到redis中
     *
     * @return
     */
    @Bean
    public RedisTokenStore redisTokenStore() {
        return new RedisTokenStore(redisConnectionFactory);
    }


}
